package simplifiedPastry;

import java.io.Serializable;
import java.math.BigInteger;

import simplifiedPastry.Node.NodeEntry;

public class RoutingTable implements Serializable {
	private static final long serialVersionUID = -7763526363239964898L;
	private NodeEntry[][]	routingTable;
	private int rtSize;				//ile jest nodeEntry w tablicy
	private NodeId nodeId;			//nr tego Node, ktory posiada ta tablice routingu
	private NodeEntry nodeEntry;	//NodeEntry tego noda, ktory posiada ta tablice routingu
	
	//NodeEntry to NodeId, InetAddress, oraz port
	
	public RoutingTable(NodeEntry nodeEntry) {
		routingTable		= new NodeEntry[32][16];	
		rtSize				= 0;						//na poczatku nie ma zadnych wpisow w rt
		this.nodeEntry		= nodeEntry;				
		this.nodeId			= nodeEntry.nodeId;
	}
	
	
	
	/**
	 * Wypelnia buff uwzgledniajac offset NodeEntry z rt, ktore nie sa null.
	 * Jesli wszystkie sa null, to nic nie robi.
	 */
	synchronized public void fillRoutingTableBuff(byte[] buff, int offset) {
		if (rtSize == 0) {
			return;
		}
		NodeEntry[] rtArray = new NodeEntry[rtSize];
		int rtIndex = 0;
		for(int i = 0; i < 32; ++i) {
			for (int j = 0; j < 16; ++j) {
				if(routingTable[i][j] != null) {
					rtArray[rtIndex++] = routingTable[i][j];
				}
				
			}
		}
		for(int i = 0; i < rtSize; ++i) {
			rtArray[i].fillByteBuff(buff, offset);	//kopiuje do buff (NodeId, InetAddress, port) w bajtach
													//z wszystkich NodeEntry co sa w rt. 
			offset += 22;
		}
	}
	
	
	/**
	 * Ta funkcja powinna zwrocic juz najlepszy NodeEntry (z najwieksza liczba grup bitow taka sama
	 * jak w searchId, ktory jest identyfikatorem odbiorcy pakietu)
	 * Zwraca null, gdy:
	 * rt[row][col] == null i nie ma zadnego blizszego node na tym poziomie (row)
	 */
	synchronized public NodeEntry getNextNodeEntry(NodeId searchId) {
		if (searchId.compareTo(nodeId) == 0) { 			//nie mozna podawac swojego id przy przeszukiwaniu
														//ale moze byc tak, ze zostanie wygenerowany taki sam id
			System.err.println("getNextNodeEntry(NodeId searchId) --should never happen!");
		}
		int row = nodeId.routingTableRow(searchId);				//pierwszy napotkany wiersz, ktory rozni sie
																//na 'row' grupie bitow w nodeId i searchId.
																//row nalezy do zbioru <0;32). Grupa 4 bitow.
		
		int col = nodeId.routingTableColumn(searchId, row);		//zwraca jaka jest wartosc w searchId pod
																//wskazanym 'row'. Moze byc <0;15)
		//teraz mamy [row][col] -> miejsce, w ktore mozna by wlozyc searchId.
		
		NodeEntry closestEntry = nodeEntry;
		if (routingTable[row][col] != null) {			//jesli w rt jest juz pod miejscem [row][col], wskazywanym
														//przez searchId jakis wpis, zwracamy go.
			return routingTable[row][col];
		} else {
			BigInteger closestDistance = nodeEntry.nodeId.distance(searchId);
			for(int i = 0; i < 16; ++i) {
				if (routingTable[row][i] != null) {
					BigInteger distance = routingTable[row][i].nodeId.distance(searchId);
					if (distance.compareTo(closestDistance) < 0) {
						closestDistance = distance;
						closestEntry = routingTable[row][i];
					}
				}
			}
		}
		if (closestEntry == nodeEntry) {
			return null;
		}
		int sameGroupNum1 = nodeId.sameGroupNum(searchId);
		int sameGroupNum2 = closestEntry.nodeId.sameGroupNum(searchId);
		if (sameGroupNum1 > sameGroupNum2) {
			System.out.println("Niemozliwa sytuacja: 1 rt");
			return null;
		} else if (sameGroupNum1 == sameGroupNum2) {
			BigInteger d1 = nodeId.distance(searchId);
			BigInteger d2 = closestEntry.nodeId.distance(searchId);
			if (d1.compareTo(d2) < 0) {
				System.out.println("Niemozliwa sytuacja: 2 rt");
				return null;
			}
		}
		return closestEntry;
	}
	
	synchronized public int getSize() {
		return rtSize;
	}
	
	synchronized public void addNodeEntry(NodeEntry nodeEntry) {
		if (nodeEntry.nodeId.compareTo(nodeId) == 0) {
			return ;
		}
		int row = nodeId.routingTableRow(nodeEntry.nodeId);
		int col = nodeId.routingTableColumn(nodeEntry.nodeId, row);
		if (routingTable[row][col] == null) {
			++rtSize;
		}
		routingTable[row][col] = nodeEntry;
	}
	
	synchronized public void removeNodeEntry(NodeEntry nodeEntry) {
		if (nodeEntry.nodeId.compareTo(nodeId) == 0) {
			System.err.println("removeNodeEntry(NodeEntry nodeEntry) --should never happen!");
		}
		int row = nodeId.routingTableRow(nodeEntry.nodeId);
		int col = nodeId.routingTableColumn(nodeEntry.nodeId, row);
		if (routingTable[row][col].nodeId.compareTo(nodeEntry.nodeId) == 0) {
			if (routingTable[row][col] != null) {
				--rtSize;
			}
			routingTable[row][col] = null;
		}
	}
	
	synchronized public String toString() {
		String out = "";
		for(int i = 0; i < 32; ++i) {
			for(int j = 0; j < 16; ++j) {
				if(routingTable[i][j] == null) {
					//out += "(null)";
					continue;
				}
				else {
					out += 	"[" + String.valueOf(i) + "][" + String.valueOf(j) + "] = " + routingTable[i][j].nodeId.toString() + "\n";
				}
			}
		}
		
		return out;
	}
	
	synchronized public NodeEntry[] getRTEntries() {
		if (rtSize == 0) {
			return new NodeEntry[0];
		}
		NodeEntry[] rtArray = new NodeEntry[rtSize];
		int rtIndex = 0;
		for(int i = 0; i < 32; ++i) {
			for (int j = 0; j < 16; ++j) {
				if(routingTable[i][j] != null) {
					rtArray[rtIndex++] = routingTable[i][j];
				}
			}
		}
		return rtArray;
	}

}
